#region File Description
//-----------------------------------------------------------------------------
// Shape.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
#endregion

namespace TimeWar
{
    /// <summary>
    /// Shape is the base class of any object that is renderable
    /// </summary>
    public abstract class Shape : IDisposable
    {
        /// <summary>
        /// The vertex buffer used by this shape
        /// </summary>
        protected VertexBuffer buffer;
        protected VertexDeclaration vertexDecl;

        /// <summary>
        /// The current world matrix used to render this shape
        /// </summary>
        protected Matrix world;
        protected Vector3 position;

        private Game game = null;

        protected Model shape;

        /// <summary>
        /// The Object's Rotation in 3D Space
        /// </summary>
        protected Quaternion objRotation = Quaternion.Identity;

        /// <summary>
        /// The Objects Scale
        /// </summary>
        protected float objScaleX;
        protected float objScaleY;
        protected float objScaleZ;

        protected float aspectRatio;

        protected String modelLocation = null;

        protected Matrix view;
        protected Matrix projection;

        protected Vector3 direction;
        protected Vector3 up;

        protected Vector3 right;        
        
        /// <summary>
        /// Full speed at which ship can rotate; measured in radians per second.
        /// </summary>
        private const float rotationRate = 1.5f;

        /// <summary>
        /// Mass of ship.
        /// </summary>
        private float mass = 1.0f;

        /// <summary>
        /// Maximum force that can be applied along the ship's direction.
        /// </summary>
        private const float ThrustForce = 24000.0f;

        /// <summary>
        /// Velocity scalar to approximate drag.
        /// </summary>
        private const float DragFactor = 0.97f;

        /// <summary>
        /// Current ship velocity.
        /// </summary>
        private Vector3 velocity = Vector3.Zero;


        //protected BoundingSphere bs;
        //protected List<BoundingSphere> bs = new List<BoundingSphere>();
        
        protected BoundingSphere range;

        //protected Vector3 acceleration = Vector3.Zero;

        protected float accelerationFactor = 0;

        /// <summary>
        /// Compensation for different metric scales
        /// </summary>
        protected float velocityModFactor = TimeWarGame.Settings.FrictionFactor;

        protected ShapeTypes shapeType = ShapeTypes.Starship;
        
        #region Properties
        public Matrix World
        {
            get
            {
                return world;
            }
            set
            {
                world = value;
            }
        }

        public Matrix Projection
        {
            get
            {
                return projection;
            }
            set
            {
                projection = value;
            }
        }

        public Vector3 Position
        {
            get
            {
                return position;
            }
            set
            {
                position = value;
            }
        }

        protected Game GameInstance
        {
            get
            {
                return game;
            }
        }

        public float PositionX
        {
            get { return position.X; }
            set { position.X = value; }
        }

        public float PositionY
        {
            get { return position.Y; }
            set { position.Y = value; }
        }

        public float PositionZ
        {
            get { return position.Z; }
            set { position.Z = value; }
        }

        protected Vector3 cameraPosition = new Vector3(0.0f, 50.0f, 5000.0f);

        public Vector3 CameraPosition
        {
            get { return cameraPosition; }
            set { cameraPosition = value; }
        }

        /// <summary>
        /// The Object's Rotation in 3D Space
        /// </summary>
        public Quaternion Rotation
        {
            get { return objRotation; }
            set { objRotation = value; }
        }

        public float RotationX
        {
            get { return objRotation.X; }
            set { objRotation.X = value; }
        }

        public float RotationY
        {
            get { return objRotation.Y; }
            set { objRotation.Y = value; }
        }

        public float RotationZ
        {
            get { return objRotation.Z; }
            set { objRotation.Z = value; }
        }

        public String ModelLocation
        {
            get { return modelLocation; }
            set { modelLocation = value; }
        }

        public Vector3 Direction
        {
            get
            {
                direction.Normalize();
                return direction;
            }
            set
            {
                direction = value;
            }
        }

        public Vector3 Up
        {
            get { return up; }
            set { up = value; }
        }

        /// <summary>
        /// The Object's Scale
        /// </summary>
        public float Scale
        {
            set
            {
                objScaleX = value;
                objScaleY = value;
                objScaleZ = value;
            }
        }

        public float ScaleX
        {
            get { return objScaleX; }
            set { objScaleX = value; }
        }

        public float ScaleY
        {
            get { return objScaleY; }
            set { objScaleY = value; }
        }

        public float ScaleZ
        {
            get { return objScaleZ; }
            set { objScaleZ = value; }
        }

        public Matrix View
        {
            set { view = value; }
            get { return view; }
        }

        /// <summary>
        /// Ship's right vector.
        /// </summary>
        public Vector3 Right
        {
            get { return right; }
        }

        public Vector3 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }

        //public BoundingSphere _BoundingSphere
        //{
        //    get
        //    {
        //        BoundingSphere bs = shape.Meshes[0].BoundingSphere;
        //        float radius = bs.Radius * objScaleX;
        //        return new BoundingSphere(position, radius);
        //    }
        //}

        public BoundingSphere Range
        {
            get
            {
                return range;
            }
        }

        public ModelMeshCollection Meshes
        {
            get
            {
                return shape.Meshes;
            }
        }

        public float Mass
        {
            get
            {
                return mass;
            }
            set
            {
                mass = value;
            }
        }

        public float AccelerationFactor
        {
            get
            {
                return accelerationFactor;
            }
            set
            {
                accelerationFactor = value;
            }
        }

        public float VelocityModFactor
        {
            get
            {
                return velocityModFactor;
            }
            set
            {
                velocityModFactor = value;
            }
        }

        public ShapeTypes ShapeType
        {
            get
            {
                return shapeType;
            }
        }

        #endregion

        protected void LoadContent()
        {

            shape = TimeWarGame.ContentManager.Load<Model>(modelLocation);
            aspectRatio = 16 / 9;
        }


        /// <summary>
        /// Create Object Instance
        /// </summary>
        public void setInitialValues()
        {
            position = new Vector3(0, 0, 0);
            objScaleX = 1.0f;
            objScaleY = 1.0f;
            objScaleZ = 1.0f;

            //Based on Reiman's tutorial
            up = Vector3.Up;
            direction = new Vector3(0, 0, 1f);  //translates to forward
            right = Vector3.Right;

        }

        public void Initialize()
        {
            LoadContent();
        }

        /// <summary>
        /// Creates a new shape. Calls the virtual Create method to generate any vertex buffers etc
        /// </summary>
        public Shape(Game game, ShapeTypes typeOfShape)
        {
            this.game = game;
            Create();
	        setInitialValues();
            shapeType = typeOfShape;
        }

        /// <summary>
        /// Creates the vertex buffers etc. This routine is called on object creation and on device reset etc
        /// </summary>
        abstract public void Create();

        /// <summary>
        /// Renders the shape. Base class does nothing
        /// </summary>
        public virtual void Render()
        {
            Draw();
        }

        /// <summary>
        /// Updates the shape. Base class does nothing
        /// </summary>
        /// <param name="time">Game Time</param>
        /// <param name="elapsedTime">Elapsed game time since last call</param>
        public virtual void Update(TimeSpan time, TimeSpan elapsedTime)
        {
            //Nothing for now

            float elapsed = 1f;

            // Determine rotation amount from input
            Vector2 rotationAmount = new Vector2(RotationX, RotationY);

            // Scale rotation amount to radians per second
            rotationAmount = rotationAmount * rotationRate * elapsed;

            // Correct the X axis steering when the ship is upside down
            if (up.Y < 0)
                rotationAmount.X = -rotationAmount.X;


            // Create rotation matrix from rotation amount
            Matrix rotationMatrix = Matrix.CreateFromAxisAngle(Right, rotationAmount.Y) * Matrix.CreateRotationY(rotationAmount.X);
            //Matrix rotationMatrix = Matrix.CreateFromQuaternion(objRotation);
            objRotation = Quaternion.CreateFromRotationMatrix(rotationMatrix);

            direction = Vector3.Transform(Direction, objRotation);
            up = Vector3.Transform(up, objRotation);
            right = Vector3.Transform(right, objRotation);

            direction.Normalize();
            up.Normalize();
            right.Normalize();

            // Reconstruct the ship's world matrix
            world = Matrix.Identity;
            world.Forward = Direction;
            world.Up = up;
            world.Right = right;
            world.Translation = position;

        }

        /// <summary>
        /// Creates a rectangle with a certain number of rows and columns. The buffer is PositionOnly and always 0.0-1.0
        /// </summary>
        /// <param name="Columns">Number of columns</param>
        /// <param name="Rows">Number of Rows</param>
        /// <returns>Populated Vertex buffer</returns>
        public VertexBuffer Plane(int Columns, int Rows)
        {
            //TODO: Would be better to have a PositionOnly vertex type here but that's not in this build
            //Its used for the backdrop of evolved which is a full screen quad with texture coordinates derived from
            //other shader variables
            //TODO: Fix up shader for evolved backdrop to ignore the color in the vertex format
            IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)game.Services.GetService(typeof(IGraphicsDeviceService));

            VertexBuffer Buffer = new VertexBuffer(graphicsService.GraphicsDevice, typeof(VertexPositionColor), Columns * Rows * 6, BufferUsage.WriteOnly);
            //VertexPositionColor data = Buffer.Lock<PositionOnly>(0, 0, LockFlags.None);
            VertexPositionColor[] data = new VertexPositionColor[Columns * Rows * 6];
            //Buffer coordinates are 0.0-1.0 so we can use them as the base texture coordinates too
            int pointCount = 0;
            for (int x = 0; x < Columns; x++)
            {
                for (int y = 0; y < Rows; y++)
                {
                    data[pointCount + 0] = new VertexPositionColor(new Vector3((float)x / (float)Columns, (float)y / (float)Rows, 0), Color.White);
                    data[pointCount + 1] = new VertexPositionColor(new Vector3((float)(x + 1) / (float)Columns, (float)y / (float)Rows, 0), Color.White);
                    data[pointCount + 2] = new VertexPositionColor(new Vector3((float)(x + 1) / (float)Columns, (float)(y + 1) / (float)Rows, 0), Color.White);
                    data[pointCount + 3] = new VertexPositionColor(new Vector3((float)x / (float)Columns, (float)y / (float)Rows, 0), Color.White); //same as 0
                    data[pointCount + 4] = new VertexPositionColor(new Vector3((float)(x + 1) / (float)Columns, (float)(y + 1) / (float)Rows, 0), Color.White); //same as 2
                    data[pointCount + 5] = new VertexPositionColor(new Vector3((float)x / (float)Columns, (float)(y + 1) / (float)Rows, 0), Color.White);

                    pointCount += 6;
                }
            }
            Buffer.SetData<VertexPositionColor>(data);

            return Buffer;
        }

        /// <summary>
        /// Called when a device is created
        /// </summary>
        public virtual void OnCreateDevice()
        {
            Create();
        }

        protected virtual void Dispose(bool all)
        {
            if (buffer != null)
            {
                buffer.Dispose();
                buffer = null;
            }

            if (vertexDecl != null)
            {
                vertexDecl.Dispose();
                vertexDecl = null;
            }
        }

        public virtual void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void Move(TimeSpan elapsedTime)
        {
            //calculate distance and apply translate
            Vector3 distanceMoved;
            double elapsedSeconds = elapsedTime.Milliseconds;
            //velocityModFactor = 1;
            Vector3 accelerationVector = Vector3.Multiply(Direction, 0.5f * Convert.ToSingle(Math.Pow(elapsedSeconds * velocityModFactor, 2)) * accelerationFactor);
            //distanceMoved = Vector3.Multiply(velocity, Convert.ToSingle(velocityModFactor * elapsedSeconds));
            //distanceMoved = Vector3.Add(distanceMoved, accelerationVector);
            velocity = Vector3.Add(velocity, Vector3.Multiply(accelerationVector, Convert.ToSingle(velocityModFactor * elapsedSeconds)));
            distanceMoved = Vector3.Multiply(velocity, Convert.ToSingle(elapsedSeconds * velocityModFactor));
            Translate(distanceMoved);

        }

        /// <summary>
        /// Move Object Forwards (Object's Z Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void MoveForwards(float distance)
        { Translate(new Vector3(0.0f, 0.0f, distance)); }

        /// <summary>
        /// Move Object Backwards (Object's Z Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void MoveBackwards(float distance)
        { Translate(new Vector3(0.0f, 0.0f, -distance)); }

        /// <summary>
        /// Strafe Object Left (Object's X Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeLeft(float distance)
        { Translate(new Vector3(-distance, 0.0f, 0.0f)); }

        /// <summary>
        /// Strafe Object Right (Object's X Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeRight(float distance)
        { Translate(new Vector3(distance, 0.0f, 0.0f)); }

        /// <summary>
        /// Strafe Object Up (Object's Y Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeUp(float distance)
        { Translate(new Vector3(0.0f, distance, 0.0f)); }

        /// <summary>
        /// Strafe Object Down (Object's Y Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeDown(float distance)
        { Translate(new Vector3(0.0f, -distance, 0.0f)); }

        /// <summary>
        /// Roll Object Left (Object's Z Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RollLeft(float angleDegrees)
        {
            //Rotate(Vector3.Forward, MathHelper.ToRadians(-angleDegrees));
            Rotate(Direction, MathHelper.ToRadians(-angleDegrees));
        }

        /// <summary>
        /// Roll Object Right (Object's Z Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RollRight(float angleDegrees)
        {
            //Rotate(Vector3.Forward, MathHelper.ToRadians(angleDegrees));
            Rotate(Direction, MathHelper.ToRadians(angleDegrees));

        }

        /// <summary>
        /// Pitch Object Down (Object's X Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void PitchDown(float angleDegrees)
        { Rotate(Vector3.Left, MathHelper.ToRadians(angleDegrees)); }

        /// <summary>
        /// Pitch Object Up (Object's X Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void PitchUp(float angleDegrees)
        { Rotate(Vector3.Left, MathHelper.ToRadians(-angleDegrees)); }

        /// <summary>
        /// Rotate Object Left (Object's Y Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RotateLeft(float angleDegrees)
        {
            //Rotate(Vector3.Up, MathHelper.ToRadians(angleDegrees));
            Rotate(up, MathHelper.ToRadians(angleDegrees));
        }

        /// <summary>
        /// Turn Object Right (Object's Y Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RotateRight(float angleDegrees)
        { 
            //Rotate(Vector3.Up, MathHelper.ToRadians(-angleDegrees));
            Rotate(up, MathHelper.ToRadians(-angleDegrees));
        }

        /// <summary>
        /// Move Object towards Target Vector
        /// </summary>
        /// <param name="target">Target Vector</param>
        /// <param name="factor">Percentage of Translation</param>
        public virtual void MoveTo(Vector3 target, float factor)
        { Position = Vector3.Lerp(Position, target, factor); }

        /// <summary>
        /// Rotate Object towards Target Quaternion
        /// </summary>
        /// <param name="slerpTarget">Target Quaternion</param>
        /// <param name="factor">Percentage of Rotation</param>
        public virtual void Slerp(Quaternion slerpTarget, float factor)
        { objRotation = Quaternion.Slerp(objRotation, slerpTarget, factor); }

        /// <summary>
        /// Rotate Object towards Target Vector
        /// </summary>
        /// <param name="target">Target Vector</param>
        /// <param name="factor">Percentage of Rotation</param>
        public virtual void LookAt(Vector3 target, float factor)
        {
            Vector3 tminusp = target - position;
            Vector3 ominusp = Vector3.Forward;

            tminusp.Normalize();

            float theta = (float)System.Math.Acos(Vector3.Dot(tminusp, ominusp));
            Vector3 cross = Vector3.Cross(ominusp, tminusp);
            string x = cross.X.ToString();
            string y = cross.Y.ToString();
            string z = cross.Z.ToString();

            cross.Normalize();
            if (cross.X.ToString().Contains("NaN")) cross.X = 0.0f;
            if (cross.Y.ToString().Contains("NaN")) cross.Y = 0.0f;
            if (cross.Z.ToString().Contains("NaN")) cross.Z = 0.0f;

            Quaternion targetQ = Quaternion.CreateFromAxisAngle(cross, theta);
            Slerp(targetQ, factor);
        }

        /// <summary>
        /// Allow the direction vector and thus the shape's direction to change by X and Y
        /// </summary>
        /// <param name="X"></param>
        /// <param name="Y"></param>
        public virtual void ChangeDirection(float X, float Y)
        {
            if (X < 0)
            {
                direction.X -= .01f;
            }
            else if (X > 0)
            {
                direction.X += .01f;
            }

            if (Y < 0)
            {
                direction.Y += .01f;
            }
            else if (Y > 0)
            {
                direction.Y -= .01f;
            }

        }

        /// <summary>
        /// Force Object to Orient to UP Vector (Y)
        /// </summary>
        /// <param name="factor">Percentage of Rotation</param>
        public void ConstrainYAxis(float factor)
        {
            Quaternion Q = new Quaternion(0, RotationY, 0, Rotation.W);
            Slerp(Q, factor);
        }

        public void Draw()
        {
            //Convert objRotation to Vector3
            //Matrix worldMatrix = Matrix.CreateScale(0.0005f, 0.0005f, 0.0005f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(objRotation) * Matrix.CreateTranslation(position);
            Matrix worldMatrix = Matrix.CreateScale(ScaleX, ScaleY, ScaleZ) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(objRotation) * Matrix.CreateTranslation(position);

            // Copy any parent transforms.
            Matrix[] transforms = new Matrix[shape.Bones.Count];
            shape.CopyAbsoluteBoneTransformsTo(transforms);

            // Draw the model. A model can have multiple meshes, so loop.
            foreach (ModelMesh mesh in shape.Meshes)
            {
                // This is where the mesh orientation is set, as well as our camera and projection.
                foreach (BasicEffect effect in mesh.Effects)
                {
                    foreach (EffectParameter param in effect.Parameters)
                    {

                        if (param.ParameterType.ToString() == "Texture")
                        {
                            foreach (EffectAnnotation annotation in param.Annotations)
                            {
                                if (annotation.Name == "ResourceName")
                                {
                                    string filename = annotation.GetValueString();
                                    //filename = Path.GetDirectoryName(filename) + @"\" + Path.GetFileNameWithoutExtension(filename);
                                    //Texture texture = Content.Load(filename);
                                    //param.SetValue(texture);
                                }
                            }
                        }

                    }


                    //Original Block:
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * worldMatrix;
                    effect.View = view;
                    effect.Projection = projection;

                }
                // Draw the mesh, using the effects set above.
                mesh.Draw();

            }
        }

        public bool hasCollidedWith(VectorShape obj)
        {
            if (null == obj)
            {
                return false;
            }
            if (null == obj.Meshes)
            {
                return false;
            }
            if (null == Meshes)
            {
                return false;
            }

            foreach (ModelMesh thisMesh in Meshes)
            {
                BoundingSphere bs = thisMesh.BoundingSphere;
                bs.Center += position;

                foreach (ModelMesh objMesh in obj.Meshes)
                {
                    BoundingSphere objBs = objMesh.BoundingSphere;
                    objBs.Center += obj.Position;
                    bool collisionDetected = bs.Intersects(objBs);

                    if (collisionDetected)
                    {
                        this.onCollision();
                        obj.onCollision();
                        return true;
                    }
                }
            }

            return false;

        }

        public abstract void onCollision();

        public Vector3 getAfterCollisionVelocity(Shape obj)
        {
            Vector3 v_b = new Vector3();
            double m_a = Convert.ToDouble(mass);
            double m_b = Convert.ToDouble(obj.Mass);
            double v_bx = Convert.ToDouble(obj.Velocity.X);
            double v_by = Convert.ToDouble(obj.Velocity.Y);
            double v_bz = Convert.ToDouble(obj.Velocity.Z);
            double v_ax = Convert.ToDouble(velocity.X);
            double v_ay = Convert.ToDouble(velocity.Y);
            double v_az = Convert.ToDouble(velocity.Z);

            if (m_a == 0)
            {
                m_a = 1;
            }
            if (m_b == 0)
            {
                m_b = 1;
            }

            v_b.X = Convert.ToSingle((Math.Pow(v_ax, 2) + m_b * v_bx + Math.Sqrt(Math.Pow(v_ax, 2) + m_b * Math.Pow(v_bx, 2))) / m_b);
            v_b.Y = Convert.ToSingle((Math.Pow(v_ay, 2) + m_b * v_by + Math.Sqrt(Math.Pow(v_ay, 2) + m_b * Math.Pow(v_by, 2))) / m_b);
            v_b.Z = Convert.ToSingle((Math.Pow(v_az, 2) + m_b * v_bz + Math.Sqrt(Math.Pow(v_az, 2) + m_b * Math.Pow(v_bz, 2))) / m_b);

            velocity.X = Convert.ToSingle( v_ax + m_b * (v_bx - Convert.ToDouble(v_b.X) / m_a ));
            velocity.Y = Convert.ToSingle(v_ay + m_b * (v_by - Convert.ToDouble(v_b.Y) / m_a));
            velocity.Z = Convert.ToSingle(v_az + m_b * (v_bz - Convert.ToDouble(v_b.Z) / m_a));

            return v_b;

        }

        protected void accelerate()
        {
            if (accelerationFactor == 0)
            {
                accelerationFactor = 0.005f;
            }
            accelerationFactor += 0.005f;
        }

        protected void stopAccelerating()
        {
            accelerationFactor = 0;
        }

        protected void deccelerate()
        {
            accelerationFactor -= 0.005f;
            if (accelerationFactor < 0)
            {
                accelerationFactor = 0;
            }
        }

        #region Private Methods

        /// <summary>
        /// Rotate Object around specific Axis
        /// </summary>
        /// <param name="axis"></param>
        /// <param name="angle"></param>
        private void Rotate(Vector3 axis, float angle)
        {
            axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(objRotation));
            objRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * objRotation);
        }

        /// <summary>
        /// Translate Object along specific Axis
        /// </summary>
        /// <param name="distance"></param>
        private void  Translate(Vector3 distance)
        { position += Vector3.Transform(distance, Matrix.CreateFromQuaternion(objRotation)); }

        #endregion


    }
}
